<template>
  <canvas ref="canvas" id="canv"></canvas>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

// 常量定义
const num = 200;
const max = 100;
const _x = 0;
const _y = 0;
const _z = 150;

// 响应式数据
const canvas = ref(null);
const w = ref(window.innerWidth);
const h = ref(window.innerHeight);
const toX = ref(_x);
const toY = ref(_y);

// 工具函数
const dtr = (d) => (d * Math.PI) / 180;
const rnd = () => Math.sin(Math.floor(Math.random() * 360) * Math.PI / 180);

// 相机对象
const cam = {
  obj: { x: _x, y: _y, z: _z },
  dest: { x: 0, y: 0, z: 1 },
  dist: { x: 0, y: 0, z: 200 },
  ang: { cplane: 0, splane: 0, ctheta: 0, stheta: 0 },
  zoom: 1,
  disp: { x: w.value / 2, y: h.value / 2, z: 0 },
  upd() {
    this.dist.x = this.dest.x - this.obj.x;
    this.dist.y = this.dest.y - this.obj.y;
    this.dist.z = this.dest.z - this.obj.z;
    this.ang.cplane = -this.dist.z / Math.sqrt(this.dist.x * this.dist.x + this.dist.z * this.dist.z);
    this.ang.splane = this.dist.x / Math.sqrt(this.dist.x * this.dist.x + this.dist.z * this.dist.z);
    this.ang.ctheta = Math.sqrt(this.dist.x * this.dist.x + this.dist.z * this.dist.z) / Math.sqrt(this.dist.x * this.dist.x + this.dist.y * this.dist.y + this.dist.z * this.dist.z);
    this.ang.stheta = -this.dist.y / Math.sqrt(this.dist.x * this.dist.x + this.dist.y * this.dist.y + this.dist.z * this.dist.z);
  },
};

// 变换对象
const trans = {
  parts: {
    sz: (p, sz) => ({ x: p.x * sz.x, y: p.y * sz.y, z: p.z * sz.z }),
    rot: {
      x: (p, rot) => ({
        x: p.x,
        y: p.y * Math.cos(dtr(rot.x)) - p.z * Math.sin(dtr(rot.x)),
        z: p.y * Math.sin(dtr(rot.x)) + p.z * Math.cos(dtr(rot.x)),
      }),
      y: (p, rot) => ({
        x: p.x * Math.cos(dtr(rot.y)) + p.z * Math.sin(dtr(rot.y)),
        y: p.y,
        z: -p.x * Math.sin(dtr(rot.y)) + p.z * Math.cos(dtr(rot.y)),
      }),
      z: (p, rot) => ({
        x: p.x * Math.cos(dtr(rot.z)) - p.y * Math.sin(dtr(rot.z)),
        y: p.x * Math.sin(dtr(rot.z)) + p.y * Math.cos(dtr(rot.z)),
        z: p.z,
      }),
    },
    pos: (p, pos) => ({ x: p.x + pos.x, y: p.y + pos.y, z: p.z + pos.z }),
  },
  pov: {
    plane: (p) => ({
      x: p.x * cam.ang.cplane + p.z * cam.ang.splane,
      y: p.y,
      z: p.x * -cam.ang.splane + p.z * cam.ang.cplane,
    }),
    theta: (p) => ({
      x: p.x,
      y: p.y * cam.ang.ctheta - p.z * cam.ang.stheta,
      z: p.y * cam.ang.stheta + p.z * cam.ang.ctheta,
    }),
    set: (p) => ({ x: p.x - cam.obj.x, y: p.y - cam.obj.y, z: p.z - cam.obj.z }),
  },
  persp: (p) => ({
    x: (p.x * cam.dist.z) / p.z * cam.zoom,
    y: (p.y * cam.dist.z) / p.z * cam.zoom,
    z: p.z * cam.zoom,
    p: cam.dist.z / p.z,
  }),
  disp: (p, disp) => ({ x: p.x + disp.x, y: -p.y + disp.y, z: p.z + disp.z, p: p.p }),
  steps: (_obj_, sz, rot, pos, disp) => {
    let _args = trans.parts.sz(_obj_, sz);
    _args = trans.parts.rot.x(_args, rot);
    _args = trans.parts.rot.y(_args, rot);
    _args = trans.parts.rot.z(_args, rot);
    _args = trans.parts.pos(_args, pos);
    _args = trans.pov.plane(_args);
    _args = trans.pov.theta(_args);
    _args = trans.pov.set(_args);
    _args = trans.persp(_args);
    _args = trans.disp(_args, disp);
    return _args;
  },
};

// 3D 点类
class ThreeD {
  constructor(param) {
    this.transIn = {};
    this.transOut = {};
    this.transIn.vtx = param.vtx;
    this.transIn.sz = param.sz;
    this.transIn.rot = param.rot;
    this.transIn.pos = param.pos;
  }
  vupd() {
    this.transOut = trans.steps(
      this.transIn.vtx,
      this.transIn.sz,
      this.transIn.rot,
      this.transIn.pos,
      cam.disp
    );
  }
}

// 主逻辑
const init = () => {
  const ctx = canvas.value.getContext('2d');
  const varr = [];
  const calc = [];
  const vel = 0.04;
  const lim = 360;
  const diff = 200;
  const rotObj = { x: 0, y: 0, z: 0 };
  const objSz = { x: w.value / 5, y: h.value / 5, z: w.value / 5 };

  // 初始化点
  for (let i = 0; i < num; i++) {
    varr.push(new ThreeD({
      vtx: { x: rnd(), y: rnd(), z: rnd() },
      sz: { x: 0, y: 0, z: 0 },
      rot: { x: 20, y: -20, z: 0 },
      pos: {
        x: diff * Math.sin((360 * Math.random() * Math.PI) / 180),
        y: diff * Math.sin((360 * Math.random() * Math.PI) / 180),
        z: diff * Math.sin((360 * Math.random() * Math.PI) / 180),
      },
    }));
    calc.push({ x: 360 * Math.random(), y: 360 * Math.random(), z: 360 * Math.random() });
  }

  // 更新函数
  const upd = () => {
    cam.obj.x += (toX.value - cam.obj.x) * 0.05;
    cam.obj.y += (toY.value - cam.obj.y) * 0.05;
  };

  // 绘制函数
  const draw = () => {
    ctx.clearRect(0, 0, canvas.value.width, canvas.value.height);
    cam.upd();
    rotObj.x += 0.1;
    rotObj.y += 0.1;
    rotObj.z += 0.1;

    for (let i = 0; i < varr.length; i++) {
      for (let val in calc[i]) {
        if (calc[i].hasOwnProperty(val)) {
          calc[i][val] += vel;
          if (calc[i][val] > lim) calc[i][val] = 0;
        }
      }

      varr[i].transIn.pos = {
        x: diff * Math.cos((calc[i].x * Math.PI) / 180),
        y: diff * Math.sin((calc[i].y * Math.PI) / 180),
        z: diff * Math.sin((calc[i].z * Math.PI) / 180),
      };
      varr[i].transIn.rot = rotObj;
      varr[i].transIn.sz = objSz;
      varr[i].vupd();
      if (varr[i].transOut.p < 0) continue;
      const g = ctx.createRadialGradient(
        varr[i].transOut.x,
        varr[i].transOut.y,
        varr[i].transOut.p,
        varr[i].transOut.x,
        varr[i].transOut.y,
        varr[i].transOut.p * 2
      );
      ctx.globalCompositeOperation = 'lighter';
      g.addColorStop(0, 'hsla(255, 255%, 255%, 1)');
      g.addColorStop(0.5, `hsla(${i + 2}, 85%, 40%, 1)`);
      g.addColorStop(1, `hsla(${i}, 85%, 40%, 0.5)`);
      ctx.fillStyle = g;
      ctx.beginPath();
      ctx.arc(varr[i].transOut.x, varr[i].transOut.y, varr[i].transOut.p * 2, 0, Math.PI * 2, false);
      ctx.fill();
      ctx.closePath();
    }
  };

  // 动画循环
  const anim = () => {
    upd();
    draw();
    requestAnimationFrame(anim);
  };
  anim();

  // 事件监听
  const onMouseMove = (e) => {
    toX.value = (e.clientX - canvas.value.width / 2) * -0.8;
    toY.value = (e.clientY - canvas.value.height / 2) * 0.8;
  };
  const onTouchMove = (e) => {
    e.preventDefault();
    toX.value = (e.touches[0].clientX - canvas.value.width / 2) * -0.8;
    toY.value = (e.touches[0].clientY - canvas.value.height / 2) * 0.8;
  };
  const onResize = () => {
    w.value = window.innerWidth;
    h.value = window.innerHeight;
    canvas.value.width = w.value;
    canvas.value.height = h.value;
  };

  window.addEventListener('mousemove', onMouseMove);
  window.addEventListener('touchmove', onTouchMove);
  window.addEventListener('resize', onResize);

  // 清理事件监听
  onUnmounted(() => {
    window.removeEventListener('mousemove', onMouseMove);
    window.removeEventListener('touchmove', onTouchMove);
    window.removeEventListener('resize', onResize);
  });
};

// 挂载时初始化
onMounted(() => {
  canvas.value.width = w.value;
  canvas.value.height = h.value;
  init();
});
</script>

<style scoped>
#canv {
  height: 100%;
  width: 100%;
  background-image: -webkit-radial-gradient(ellipse farthest-corner at center center, #1b44e4 0%, #020f3a 100%);
  background-image: radial-gradient(ellipse farthest-corner at center center, #193cc9 0%, #030f36 100%);
  cursor: move;
  margin: 0;
  overflow: hidden;
}
</style>